iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0

在昨天的文章中,我們透過生活中的例子理解了「同步」與「非同步」的核心差異。我們知道非同步能讓程式在等待的空檔去執行其他任務,大幅提升效率。但 Python 究竟是如何實現這件事的呢?答案就在於今天的主角:協程 (Coroutine)事件迴圈 (Event Loop)

協程 (Coroutine):可以暫停的函式

協程,是 "Cooperative Routine" 的縮寫,可以理解為「可協作的常式」。它是一種特殊的函式,其特殊之處在於它可以在執行到某個點時暫停,將控制權交還出去,並在未來某個時刻從暫停的地方恢復執行。

在 Python 中,我們使用 async def 語法來定義一個協程函式:

import asyncio

async def my_coroutine():
    print("協程開始執行")
    await asyncio.sleep(1) # 在這裡暫停,交出控制權
    print("協程恢復執行")

# 注意!直接呼叫協程函式並不會執行它
# 它只會回傳一個協程物件
coro_obj = my_coroutine()
print(coro_obj)

執行上面的程式碼,你會發現它只會印出一個協程物件的資訊,而不會印出 "協程開始執行"。這就像在餐廳點餐,你拿到的是一張等待叫號的號碼單(協程物件),而不是餐點本身。你需要一個服務生(事件迴圈)來為你處理這張訂單。

事件迴圈 (Event Loop):非同步任務的總指揮

事件迴圈是 asyncio 應用程式的核心。你可以把它想像成一個任務管理者,它不斷地在迴圈中檢查:「現在有哪些任務可以執行?」

它的工作流程大致如下:

  1. 接收任務:將協程物件(如 my_coroutine())註冊到事件迴圈中。
  2. 執行任務:從任務清單中取出一個來執行。
  3. 處理等待:當任務執行到 await 關鍵字時(例如等待網路回應或 asyncio.sleep),它會暫停該任務。
  4. 切換任務:在等待的同時,事件迴圈不會閒著,它會去執行清單中的下一個任務。
  5. 恢復任務:當被暫停的任務所等待的事件完成後(例如網路資料回來了),事件迴圈會在適當的時機將其喚醒,從上次暫停的地方繼續執行。

這個不斷「執行-暫停-切換-恢復」的過程,就是非同步得以實現並行效果的根本原因。

import asyncio

async def main():
    print("主程式開始")
    # await 會告訴事件迴圈:執行 my_coroutine,並在這裡等待它完成
    await my_coroutine() 
    print("主程式結束")

async def my_coroutine():
    print("協程開始執行")
    await asyncio.sleep(1) 
    print("協程恢復執行")

# asyncio.run(main()) 就是啟動事件迴圈,並將 main 協程作為第一個任務執行的指令
asyncio.run(main())

需要特別注意的是,這個例子中事件迴圈只處理了一個協程任務(my_coroutine)。但在實際應用中,事件迴圈可以同時管理許多個協程任務,就像餐廳的服務生可以同時處理多個客人的訂單一樣。當某個任務在等待時(比如等待網路回應),事件迴圈會立刻切換到其他可執行的任務,這就是非同步程式設計真正發揮威力的地方。關於如何同時執行多個協程任務,我們明天在介紹 asyncio 時會有更完整的說明~

小結

今天,我們介紹了非同步的幾個重要觀念:

  • 協程 (async def):是可以被暫停和恢復的函式,是我們非同步任務的基本單位。
  • 事件迴圈:是任務的總調度中心,負責在協程之間切換,實現非阻塞的並行效果。
  • await:是協程向事件迴圈發出的信號,表示「我要在這裡等待,你可以先去做別的事」。

理解了這兩個核心組件後,我們明天將進一步探索 asyncio 這個 Python 內建的套件,看看它提供了哪些強大的工具來幫助我們更好地組織和管理這些非同步任務。


上一篇
[Day 03] 同步與非同步
下一篇
[Day 05] Asyncio
系列文
用 FastAPI 打造你的 AI 服務5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言